#!/usr/bin/env python
# -*- coding: UTF-8 -*-

'''Implementation of Elementary Cellular Automata

   http://en.wikipedia.org/wiki/Cellular_automaton#Elementary_cellular_automata'''

import random

from cellular_engine.constants import WORLD_TYPE_DEAD_AROUND, WORLD_TYPE_TORUS


class ElementaryCellularAutomata(object):
    def __init__(self, rule, starting_row='1', width=59, world_type=WORLD_TYPE_DEAD_AROUND):
        '''Create a new ElementaryCellularAutomata object

        rule: The rule to use. A integer between 0 and 256
        starting_row: The first row for the automata. Needs to be a string 
                      representing the row, for example: '111001'
        width: Amount of cells in one line, must be a uneven integer'''

        if rule > 256 or rule < 0:
            raise ValueError('Invalid number for rule must be between 0 and 256')

        if len(starting_row) > width:
            raise ValueError('Too many cells in the starting row!')

        width = int(width)
        if not width % 2:
            raise ValueError('The width must be uneven!')

        if isinstance(starting_row, list) and len(starting_row) == 1:
            starting_row = starting_row[0]

        self.rule = rule
        self.width = width
        self.starting_row = starting_row.center(width, '0')
        self.world_type = world_type

        if world_type == WORLD_TYPE_DEAD_AROUND:
            self.get_left_cells = lambda previous_row: '0%s' % previous_row[0:2]
            self.get_right_cells = lambda previous_row: '%s0' % previous_row[-2:]
        elif world_type == WORLD_TYPE_TORUS:
            self.get_left_cells = lambda previous_row: '%s%s' % (previous_row[-1], previous_row[0:2])
            self.get_right_cells = lambda previous_row: '%s%s' % (previous_row[0], previous_row[-2:])

        self.rows = [self.starting_row]
        self.generation = 0

        # Convert the rule to what we need internally
        binary = bin(rule)[2:].zfill(8)

        self._rule = {'111': binary[0], '110': binary[1], '101': binary[2],
                      '100': binary[3], '011': binary[4], '010': binary[5],
                      '001': binary[6], '000': binary[7]}

    def __repr__(self):
        return '<ECA Rule %i Generation %i>' % (self.rule, self.generation)

    @staticmethod
    def get_random_row(length):
        return ''.join([random.choice(('0', '1')) for _ in range(length)])

    def game_loop(self):
        previous_row = self.rows[-1]
        current_generation = []
        rule = self._rule

        current_generation.append(rule[self.get_left_cells(previous_row)])

        # Iterate over all characters except the first and last one
        for i in range(1, len(previous_row) - 1):
            cells = previous_row[i - 1:i + 2]
            current_generation.append(rule[cells])

        current_generation.append(rule[self.get_right_cells(previous_row)])

        current_generation = ''.join(current_generation).center(self.width, '0')
        self.rows.append(current_generation)
        self.generation += 1

    def reset(self):
        '''Set everything back to square 0'''
        self.rows = [self.starting_row]
        self.generation = 0
